﻿//===========================[ 7 / 6 / 2011 LVI ]==========================
//				Copyright c Vitaly Lyaschenko <SkyChameleon@gmail.com>
//								Source file
// Purpose: 
//=========================================================================
#include "gl3com.h"
#include "cRenderDevice.h"
#include "cMeshManager.h"
#include "cFileManager.h"
#include "cwLogger.h"

cMeshesManager  *pMeshManager    = nullptr;
cProgramManager *pProgramManager = nullptr;

// атрибуты WGL контекста
int attribsWGLcontex[] =
{
    WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
    WGL_CONTEXT_MINOR_VERSION_ARB, 3,
    WGL_CONTEXT_FLAGS_ARB,         WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
    WGL_CONTEXT_PROFILE_MASK_ARB,  WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
    0
};

cRenderDevice::cRenderDevice(void) :
    hWnd(nullptr),
    hDC(nullptr),
    m_hGLRC(nullptr)
{	
    // установка параметра вывода примитивов
    m_prim.mode = GL_TRIANGLES;
    m_prim.offset = 0;
    m_prim.size = 3;

    pMeshManager = new cMeshesManager;
    if ( !pMeshManager )
        cwLogger::writeERROR("ERROR Create Mesh Manager!");

    pProgramManager = new cProgramManager;
    if ( !pProgramManager )
        cwLogger::writeERROR("ERROR Create Program Manager!");
    
    cwLogger::writeINFO("< Render Device > Instance Create.");
}

cRenderDevice::~cRenderDevice(void)
{   
    SAFE_DELETE(pMeshManager);
    SAFE_DELETE(pProgramManager);

    release();

    cwLogger::writeINFO("< Render Device > Instance Destroy.");
}
///////////////////////////////////////////////////////////////////////////
// инициализация рендера
bool cRenderDevice::init( HWND hWindow, DisplayCfg *setting )
{
    assert(hWindow);
    hWnd = hWindow;
    hDC = GetDC( hWindow );		// получить дескриптор контекста окна
    if(hDC == nullptr)
    {
        cwLogger::writeERROR("Get Device Context failed!");
        return false;
    }
        
    pSetting = setting;

    // создает объект описывающий пиксельный формат  
    // http://opengl.gamedev.ru/articles/?id=003
    // http://msdn.microsoft.com/en-us/library/dd318284.aspx
    PIXELFORMATDESCRIPTOR pfd = {		
        sizeof(PIXELFORMATDESCRIPTOR),	// size of this pfd 
        1,								// version number 
        PFD_DRAW_TO_WINDOW |			// support window 
        PFD_SUPPORT_OPENGL |			// support OpenGL		
        PFD_DOUBLEBUFFER,				// double buffered		
        PFD_TYPE_RGBA,					// RGBA type			
        32,								// color depth			
        0, 0, 0, 0, 0, 0,				// color bits ignored 
        0,								// no alpha buffer 
        0,								// shift bit ignored 
        0,								// no accumulation buffer 
        0, 0, 0, 0,						// accum bits ignored 
        24,								// z-buffer 
        8,								// stencil buffer 
        0,								// no auxiliary buffer 
        PFD_MAIN_PLANE,					// main layer 
        0,								// reserved 
        0, 0, 0							// layer masks ignored 
    };
    // запрос на выбор наиболее подходящего пиксельного формата
    int pixelformat = ChoosePixelFormat( hDC, &pfd );
    if( pixelformat == 0 )
    {
        cwLogger::writeERROR("Unable to chose pixel format");
        return false;
    }
    // устанавливаем  пиксельный формат	
    if( !SetPixelFormat(hDC, pixelformat, &pfd) )
    {
        cwLogger::writeERROR(" Unable to set pixel format");
        return false;
    }
    // временный контекст
    HGLRC tmp_hGLRC;
    tmp_hGLRC = wglCreateContext( hDC );

    if( !tmp_hGLRC ||  !wglMakeCurrent(hDC,  tmp_hGLRC) )
    {
        cwLogger::writeERROR("Unable to create temp OpenGL Context > " + GetLastError() );
        return false;
    }

    if ( !InitGLEW() )
        return false;

    graphicInformationSystem();

    if ( !InitDevIL() )
        return false;

    wglMakeCurrent(nullptr, nullptr);
    wglDeleteContext(tmp_hGLRC);

    // создать просчитывающий контекст (rendering context) OpenGL			
    if( (m_hGLRC = wglCreateContextAttribsARB(hDC, 0, attribsWGLcontex) ) == nullptr )
    {
        cwLogger::writeERROR("Unable to create OpenGL Context");
        cwLogger::writeERROR( "OpenGL 3.3 not supported!" );
        DebugMessage("Sorry, but your video card not support OpenGL 3.3 or you need to update video driver!");
        return false;
    }
    
    // выставить созданный контекст	
    if( wglMakeCurrent(hDC, m_hGLRC) == FALSE )
    {
        cwLogger::writeERROR("Unable to select OpenGL Context ");
        return false;
    }

    // отсечения невидемых граней	
    glCullFace(GL_BACK);
    // 
    glFrontFace(GL_CCW);
    // полноэкранное сглаживание	
    glEnable(GL_MULTISAMPLE);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    cwLogger::writeINFO("< RenderDevice > Initialize Success!");

    return true;
}

bool cRenderDevice::release()
{
    if (m_hGLRC)
        wglDeleteContext(m_hGLRC);
    if (hDC)
        ReleaseDC(hWnd, hDC);

    return true;
}
///////////////////////////////////////////////////////////////////////////
// возвращает Vertex Array Object VAO
cVAO *cRenderDevice::createVArray()
{   
    cVAO *vao = new cVAO();
    vao->Create();
    return vao;
}
///////////////////////////////////////////////////////////////////////////
// возвращает Vertex Buffer Object VBO
cVBO  *cRenderDevice::createVBuffer( size_t size,const void *pData,usageBuffer usage )
{
    return new cVBO( gl_ARRAY_BUFFER,size,pData,usage ); 
}

cVBO *cRenderDevice::createVBufferEl( size_t size,const void *pData,usageBuffer usage )
{
    return new cVBO( gl_ELEMENT_ARRAY_BUFFER,size,pData,usage ); 
}

void cRenderDevice::setAttrPtr( uint program, const char * name, int numComponents, int stride, void * ptr, bool normalized )
{
    // получить индекс атрибута из шейдерной программы
    int	location = glGetAttribLocation ( program, name );
    if( location < 0 ) 
        return;
        
    // параметры вершинного атрибута для активного VBO
    glVertexAttribPointer( location,             // index
                           numComponents,        // number of values per vertex
                           GL_FLOAT,             // type (GL_FLOAT)
                           normalized ? GL_TRUE : GL_FALSE,
                           stride,               // stride (offset to next vertex data)
                           (const void*)ptr );
    // разрешить использовать вершинный атрибут
    glEnableVertexAttribArray ( location );
}

void cRenderDevice::setAttrPtr( int vertLoc, int numComponents, int stride, void * ptr, bool normalized )
{
    // параметры вершинного атрибута для активного VBO
    glVertexAttribPointer( vertLoc,              // index
                           numComponents,        // number of values per vertex
                           GL_FLOAT,             // typeVar (GL_FLOAT)
                           normalized ? GL_TRUE : GL_FALSE,
                           stride,               // stride (offset to next vertex data)
                           (const void*)ptr );
    // разрешить использовать вершинный атрибут
    glEnableVertexAttribArray ( vertLoc );
}

void cRenderDevice::setViewport(int x, int y, int width, int height)
{
    RECT rect;
    GetWindowRect(hWnd, &rect);
    float ax = (float)(rect.right-rect.left) / pSetting->width;
    float ay = (float)(rect.bottom-rect.top) / pSetting->height;
    glViewport((GLint)(x*ax), (GLint)(y*ay), (GLint)(width*ax), (GLint)(height*ay));
}
///////////////////////////////////////////////////////////////////////////
// устанавливает способ вывода примитивов
void cRenderDevice::setDrawPrimitiveMode( drawMode mode )
{
    switch(mode)
    {
        case Points:
            {
                m_prim.mode   = GL_POINT;
                m_prim.offset = 0;
                m_prim.size   = 1;
            } break;
        case Lines:
            {
                m_prim.mode   = GL_LINES;
                m_prim.offset = 0;
                m_prim.size   = 2;
            } break;
        case LineStrip:
            {
                m_prim.mode   = GL_LINE_STRIP;
                m_prim.offset = 1;
                m_prim.size   = 1;
            } break;
        case Triangles:
            {
                m_prim.mode   = GL_TRIANGLES;
                m_prim.offset = 0;
                m_prim.size   = 3;
            } break;
        case TriangleStrip:
            {
                m_prim.mode   = GL_TRIANGLE_STRIP;
                m_prim.offset = 2;
                m_prim.size   = 1;
            } break;
        default:
            break;
    }
}

void cRenderDevice::setFillMode( fillMode mode)
{
    switch(mode)
    {
    case Fill_Point:
        glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
        break;
    case Fill_Wireframe:
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        break;
    case Fill_Solid:
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        break;
    default:
        break;
    }
}

void cRenderDevice::drawArrays( uint startVertex, int primitiveCount )
{
    glDrawArrays( m_prim.mode, startVertex, primitiveCount * m_prim.size + m_prim.offset );
}
///////////////////////////////////////////////////////////////////////////
// для индексов размером в 16 байт 
void cRenderDevice::drawElements16( int primitiveCount )
{
    glDrawElements( m_prim.mode,  primitiveCount * m_prim.size, GL_UNSIGNED_SHORT, nullptr );
}
///////////////////////////////////////////////////////////////////////////
// для индексов размером в 32 байта
void cRenderDevice::drawElements32( int primitiveCount )
{
    glDrawElements( m_prim.mode,  primitiveCount * m_prim.size, GL_UNSIGNED_INT, nullptr );
}

void cRenderDevice::clear( /*vec4 color, float depth, uint stencil*/ )
{
    //glClearColor(color.x, color.y, color.z, color.w); // RGBA
    glClearColor( 0.f, 0.f, 1.f, 1.f );
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    //glClearDepth(depth);
    //glClearStencil(stencil);
}
///////////////////////////////////////////////////////////////////////////
// смена буферов задний <-> передний
void cRenderDevice::swapBuffers()
{
    glFinish();
    SwapBuffers(hDC);
}
///////////////////////////////////////////////////////////////////////////
// создает шейдерную программу и помещает в стек
void cRenderDevice::createProgram( const char* VertFile, const char* fragFile, const char* nameProg )
{
    //cProgramManager::spec().pushProgram(VertFile,fragFile);
    cProgramManager::createProgram( VertFile,fragFile,nameProg );
}
///////////////////////////////////////////////////////////////////////////
// возвращает id шейдерной программы по ключу "nameProgram"
cShaderProgram * cRenderDevice::getProgram( const char* nameProgram )
{
    //return cProgramManager::spec().popProgram(num);
    return cProgramManager::at( nameProgram );
}
///////////////////////////////////////////////////////////////////////////
// создает текстуру
cTexture * cRenderDevice::createTexture( int minFilter, int magFilter, int warp, uint target, cImage *image, bool MIPs )
{
    cTexture *pTexture = new cTexture;

    pTexture->CreateTexture( minFilter, magFilter, warp, target );
    pTexture->SetImage2D( image, MIPs );

    return pTexture;
}
///////////////////////////////////////////////////////////////////////////
// делает активной текстурный юнит 0..
void cRenderDevice::setTexture( cTexture *pTexture, cShaderProgram *program, uint texUnit )
{
    if(pTexture)
    {
        glEnable( pTexture->getTargetTexture() );
        pTexture->BindTexture( texUnit );
        // устанавливаем юниформ текстуры
        program->setUniform1i( pTexture->getLocationInProgram(), texUnit );
    }
    else{
        glDisable( pTexture->getTargetTexture() );
    }
}

void cRenderDevice::setLight( cShaderProgram *pProgram, cLight &light )
{
    ASSERT( pProgram );

    pProgram->setUniform4f( "light.ambient",  light.ambient  );
    pProgram->setUniform4f( "light.diffuse",  light.diffuse  );
    pProgram->setUniform4f( "light.specular", light.specular );

    switch( light.type )
    {
    case Light_Point:
        {
            pProgram->setUniform4f( "light.position", light.position );
        } break;
    case Light_Directional:
        {
            pProgram->setUniform4f( "light.spotDirection", light.spotDirection );
        } break;
    case Light_Spot:
        {
            pProgram->setUniform4f( "light.position", light.position );
            pProgram->setUniform4f( "light.spotDirection", light.spotDirection );
        }
    default:
        break;
    }
}

void cRenderDevice::setMaterial( cShaderProgram *pProgram, const cMaterial *material, int texId )
{
    ASSERT( pProgram);

    glActiveTexture( GL_TEXTURE0 + texId );
    glBindTexture( GL_TEXTURE_2D, material->texture );

    pProgram->setUniform1i( "material.colorTexture", texId );

    pProgram->setUniform4f( "material.ambient",    material->ambient  );
    pProgram->setUniform4f( "material.diffuse",    material->diffuse  );
    pProgram->setUniform4f( "material.specular",   material->specular );
    pProgram->setUniform4f( "material.emission",   material->emission );

    pProgram->setUniform1f( "material.shininess", material->shininess );
}


